/* Exception handling and frame unwind runtime interface routines.
   Copyright (C) 2001-2025 Free Software Foundation, Inc.

   This file is part of GCC.

   GCC is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   GCC is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
   License for more details.

   Under Section 7 of GPL version 3, you are granted additional
   permissions described in the GCC Runtime Library Exception, version
   3.1, as published by the Free Software Foundation.

   You should have received a copy of the GNU General Public License and
   a copy of the GCC Runtime Library Exception along with this program;
   see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
   <http://www.gnu.org/licenses/>.  */

#ifndef _UNWIND_PE_H_
#define _UNWIND_PE_H_

/* @@@ Really this should be out of line, but this also causes link
   compatibility problems with the base ABI.  This is slightly better
   than duplicating code, however.  */

#ifndef GCC_UNWIND_PE_H
#define GCC_UNWIND_PE_H

/* If using C++, references to abort have to be qualified with std::.  */
#if __cplusplus
#define __gxx_abort std::abort
#else
#define __gxx_abort abort
#endif

/* Pointer encodings, from dwarf2.h.  */
#define DW_EH_PE_absptr 0x00
#define DW_EH_PE_omit   0xff

#define DW_EH_PE_uleb128 0x01
#define DW_EH_PE_udata2  0x02
#define DW_EH_PE_udata4  0x03
#define DW_EH_PE_udata8  0x04
#define DW_EH_PE_sleb128 0x09
#define DW_EH_PE_sdata2  0x0A
#define DW_EH_PE_sdata4  0x0B
#define DW_EH_PE_sdata8  0x0C
#define DW_EH_PE_signed  0x08

#define DW_EH_PE_pcrel   0x10
#define DW_EH_PE_textrel 0x20
#define DW_EH_PE_datarel 0x30
#define DW_EH_PE_funcrel 0x40
#define DW_EH_PE_aligned 0x50

#define DW_EH_PE_indirect 0x80

#ifndef NO_SIZE_OF_ENCODED_VALUE

/* Given an encoding, return the number of bytes the format occupies.
   This is only defined for fixed-size encodings, and so does not
   include leb128.  */

static unsigned int
size_of_encoded_value(unsigned char encoding) __attribute__((unused));

static unsigned int
size_of_encoded_value(unsigned char encoding) {
  if (encoding == DW_EH_PE_omit)
    return 0;

  switch (encoding & 0x07) {
    case DW_EH_PE_absptr:
      return sizeof(void*);
    case DW_EH_PE_udata2:
      return 2;
    case DW_EH_PE_udata4:
      return 4;
    case DW_EH_PE_udata8:
      return 8;
  }
  __gxx_abort();
}

#endif

#ifndef NO_BASE_OF_ENCODED_VALUE

/* Given an encoding and an _Unwind_Context, return the base to which
   the encoding is relative.  This base may then be passed to
   read_encoded_value_with_base for use when the _Unwind_Context is
   not available.  */

static _Unwind_Ptr
base_of_encoded_value(unsigned char encoding, struct _Unwind_Context* context) {
  if (encoding == DW_EH_PE_omit)
    return 0;

  switch (encoding & 0x70) {
    case DW_EH_PE_absptr:
    case DW_EH_PE_pcrel:
    case DW_EH_PE_aligned:
      return 0;

    case DW_EH_PE_textrel:
      return _Unwind_GetTextRelBase(context);
    case DW_EH_PE_datarel:
      return _Unwind_GetDataRelBase(context);
    case DW_EH_PE_funcrel:
      return _Unwind_GetRegionStart(context);
  }
  __gxx_abort();
}

#endif

/* Read an unsigned leb128 value from P, store the value in VAL, return
   P incremented past the value.  We assume that a word is large enough to
   hold any value so encoded; if it is smaller than a pointer on some target,
   pointers should not be leb128 encoded on that target.  */

static const unsigned char*
read_uleb128(const unsigned char* p, _uleb128_t* val) {
  unsigned int  shift = 0;
  unsigned char byte;
  _uleb128_t    result;

  result = 0;
  do {
    byte = *p++;
    result |= ((_uleb128_t)byte & 0x7f) << shift;
    shift += 7;
  } while (byte & 0x80);

  *val = result;
  return p;
}

/* Similar, but read a signed leb128 value.  */

static const unsigned char*
read_sleb128(const unsigned char* p, _sleb128_t* val) {
  unsigned int  shift = 0;
  unsigned char byte;
  _uleb128_t    result;

  result = 0;
  do {
    byte = *p++;
    result |= ((_uleb128_t)byte & 0x7f) << shift;
    shift += 7;
  } while (byte & 0x80);

  /* Sign-extend a negative value.  */
  if (shift < 8 * sizeof(result) && (byte & 0x40) != 0)
    result |= -(((_uleb128_t)1L) << shift);

  *val = (_sleb128_t)result;
  return p;
}

extern _Unwind_Ptr _Unwind_gnu_Find_got(_Unwind_Ptr);

/* Load an encoded value from memory at P.  The value is returned in VAL;
   The function returns P incremented past the value.  BASE is as given
   by base_of_encoded_value for this encoding in the appropriate context.  */

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress-of-packed-member"

static const unsigned char*
read_encoded_value_with_base(unsigned char encoding, _Unwind_Ptr base,
                             const unsigned char* p, _Unwind_Ptr* val) {
  union unaligned {
    void*    ptr;
    unsigned u2 __attribute__((mode(HI)));
    unsigned u4 __attribute__((mode(SI)));
    unsigned u8 __attribute__((mode(DI)));
    signed   s2 __attribute__((mode(HI)));
    signed   s4 __attribute__((mode(SI)));
    signed   s8 __attribute__((mode(DI)));
  } __attribute__((__packed__));

  const union unaligned* u = (const union unaligned*)p;
  _Unwind_Internal_Ptr   result;

  if (encoding == DW_EH_PE_aligned) {
    _Unwind_Internal_Ptr a = (_Unwind_Internal_Ptr)p;
    a                      = (a + sizeof(void*) - 1) & -sizeof(void*);
    result                 = *(_Unwind_Internal_Ptr*)a;
    p                      = (const unsigned char*)(_Unwind_Internal_Ptr)(a + sizeof(void*));
  } else {
    switch (encoding & 0x0f) {
      case DW_EH_PE_absptr:
        result = (_Unwind_Internal_Ptr)u->ptr;
        p += sizeof(void*);
        break;

      case DW_EH_PE_uleb128:
        {
          _uleb128_t tmp;
          p      = read_uleb128(p, &tmp);
          result = (_Unwind_Internal_Ptr)tmp;
        }
        break;

      case DW_EH_PE_sleb128:
        {
          _sleb128_t tmp;
          p      = read_sleb128(p, &tmp);
          result = (_Unwind_Internal_Ptr)tmp;
        }
        break;

      case DW_EH_PE_udata2:
        result = u->u2;
        p += 2;
        break;
      case DW_EH_PE_udata4:
        result = u->u4;
        p += 4;
        break;
      case DW_EH_PE_udata8:
        result = u->u8;
        p += 8;
        break;

      case DW_EH_PE_sdata2:
        result = u->s2;
        p += 2;
        break;
      case DW_EH_PE_sdata4:
        result = u->s4;
        p += 4;
        break;
      case DW_EH_PE_sdata8:
        result = u->s8;
        p += 8;
        break;

      default:
        __gxx_abort();
    }

    if (result != 0) {
#if __FDPIC__
      /* FDPIC relative addresses imply taking the GOT address
         into account.  */
      if ((encoding & DW_EH_PE_pcrel) && (encoding & DW_EH_PE_indirect)) {
        result += _Unwind_gnu_Find_got((_Unwind_Ptr)u);
        result = *(_Unwind_Internal_Ptr*)result;
      } else {
        result += ((encoding & 0x70) == DW_EH_PE_pcrel
                       ? (_Unwind_Internal_Ptr)u
                       : base);
        if (encoding & DW_EH_PE_indirect)
          result = *(_Unwind_Internal_Ptr*)result;
      }
#else
      result += ((encoding & 0x70) == DW_EH_PE_pcrel
                     ? (_Unwind_Internal_Ptr)u
                     : base);
      if (encoding & DW_EH_PE_indirect)
        result = *(_Unwind_Internal_Ptr*)result;
#endif
    }
  }

  *val = result;
  return p;
}

#pragma GCC diagnostic pop

#ifndef NO_BASE_OF_ENCODED_VALUE

/* Like read_encoded_value_with_base, but get the base from the context
   rather than providing it directly.  */

static inline const unsigned char*
read_encoded_value(struct _Unwind_Context* context, unsigned char encoding,
                   const unsigned char* p, _Unwind_Ptr* val) {
  return read_encoded_value_with_base(encoding,
                                      base_of_encoded_value(encoding, context),
                                      p, val);
}

#endif

#endif /* unwind-pe.h */

#endif /* _UNWIND_PE_H_ */
